package com.dbflow5.adapter;

import com.dbflow5.adapter.saveable.ListModelSaver;
import com.dbflow5.adapter.saveable.ModelSaver;
import com.dbflow5.annotation.ConflictAction;
import com.dbflow5.annotation.ForeignKey;
import com.dbflow5.annotation.Table;
import com.dbflow5.config.DBFlowDatabase;
import com.dbflow5.config.FlowLog;
import com.dbflow5.database.DatabaseStatement;
import com.dbflow5.database.DatabaseWrapper;
import com.dbflow5.query.property.IProperty;
import com.dbflow5.query.property.Property;
import ohos.data.rdb.ValuesBucket;

import java.util.Collection;

/**
 * Description: Used for generated classes from the combination of [Table] and [Model].
 */
public abstract class ModelAdapter<T> extends RetrievalAdapter<T> implements InternalAdapter<T>, CreationAdapter {

    private ModelSaver<T> _modelSaver;
    ListModelSaver<T> listModelSaver;

    public ModelAdapter(DBFlowDatabase databaseDefinition){
        super(databaseDefinition);
        listModelSaver = createListModelSaver();

        if(tableConfig != null && tableConfig.modelSaver != null) {
            ModelSaver<T> modelSaver = tableConfig.modelSaver;
            modelSaver.modelAdapter = this;
            _modelSaver = modelSaver;
        }
    }

    /**
     * An array of column properties, in order of declaration.
     *
     * @return An array of column properties, in order of declaration.
     */
    public abstract IProperty<?>[] getAllColumnProperties();

    /**
     * The query used to insert a model using a [DatabaseStatement]
     *
     * @return The query used to insert a model using a [DatabaseStatement]
     */
    protected abstract String getInsertStatementQuery();

    protected abstract String getUpdateStatementQuery();

    protected abstract String getDeleteStatementQuery();

    protected abstract String getSaveStatementQuery();

    /**
     * The conflict algorithm to use when updating a row in this table.
     *
     * @return The conflict algorithm to use when updating a row in this table.
     */
    public ConflictAction getUpdateOnConflictAction(){
        return ConflictAction.ABORT;
    }

    /**
     * The conflict algorithm to use when inserting a row in this table.
     *
     * @return The conflict algorithm to use when inserting a row in this table.
     */
    public ConflictAction getInsertOnConflictAction() {
        return ConflictAction.ABORT;
    }

    /**
     * A new compiled [DatabaseStatement] representing insert.
     *
     * @param databaseWrapper The database used to do an insert statement.
     * @return a new compiled [DatabaseStatement] representing insert. Not cached, always generated.
     * To bind values use [bindToInsertStatement].
     */
    public DatabaseStatement getInsertStatement(DatabaseWrapper databaseWrapper){
        return databaseWrapper.compileStatement(getInsertStatementQuery());
    }

    /**
     * A new compiled [DatabaseStatement] representing update.
     *
     * @param databaseWrapper The database used to do an update statement.
     * @return a new compiled [DatabaseStatement] representing update. Not cached, always generated.
     * To bind values use [bindToUpdateStatement].
     */
    public DatabaseStatement getUpdateStatement(DatabaseWrapper databaseWrapper){
        return databaseWrapper.compileStatement(getUpdateStatementQuery());
    }

    /**
     * A new compiled [DatabaseStatement] representing delete.
     *
     * @param databaseWrapper The database used to do a delete statement.
     * @return a new compiled [DatabaseStatement] representing delete. Not cached, always generated.
     * To bind values use [bindToDeleteStatement].
     */
    public DatabaseStatement getDeleteStatement(DatabaseWrapper databaseWrapper){
        return databaseWrapper.compileStatement(getDeleteStatementQuery());
    }

    /**
     * getSaveStatement
     *
     * @param databaseWrapper The database used to do a save (insert or replace) statement.
     * @return a new compiled [DatabaseStatement] representing insert or replace. Not cached, always generated.
     * To bind values use [bindToInsertStatement].
     */
    public DatabaseStatement getSaveStatement(DatabaseWrapper databaseWrapper){
        return databaseWrapper.compileStatement(getSaveStatementQuery());
    }

    @Override
    public boolean save(T model, DatabaseWrapper databaseWrapper) {
        checkInTransaction(databaseWrapper);
        return getModelSaver().save(model, databaseWrapper);
    }

    @Override
    public long saveAll(Collection<T> models, DatabaseWrapper databaseWrapper) {
        checkInTransaction(databaseWrapper);
        return listModelSaver.saveAll(models, databaseWrapper);
    }

    @Override
    public long insert(T model, DatabaseWrapper databaseWrapper) {
        checkInTransaction(databaseWrapper);
        return getModelSaver().insert(model, databaseWrapper);
    }

    @Override
    public long insertAll(Collection<T> models, DatabaseWrapper databaseWrapper) {
        checkInTransaction(databaseWrapper);
        return listModelSaver.insertAll(models, databaseWrapper);
    }

    @Override
    public boolean update(T model, DatabaseWrapper databaseWrapper) {
        checkInTransaction(databaseWrapper);
        return getModelSaver().update(model, databaseWrapper);
    }

    @Override
    public long updateAll(Collection<T> models, DatabaseWrapper databaseWrapper) {
        checkInTransaction(databaseWrapper);
        return listModelSaver.updateAll(models, databaseWrapper);
    }

    @Override
    public boolean delete(T model, DatabaseWrapper databaseWrapper) {
        checkInTransaction(databaseWrapper);
        return getModelSaver().delete(model, databaseWrapper);
    }

    @Override
    public long deleteAll(Collection<T> models, DatabaseWrapper databaseWrapper) {
        checkInTransaction(databaseWrapper);
        return listModelSaver.deleteAll(models, databaseWrapper);
    }

    private void checkInTransaction(DatabaseWrapper databaseWrapper) {
        if (!databaseWrapper.isInTransaction()) {
            FlowLog.log(FlowLog.Level.W, "Database Not Running in a Transaction. Performance may be impacted, observability " +
                "will need manual updates via db.tableObserver.checkForTableUpdates()");
        }
    }

    @Override
    public void bindToContentValues(ValuesBucket contentValues, T model) {
        bindToInsertValues(contentValues, model);
    }

    @Override
    public void bindToInsertValues(ValuesBucket contentValues, T model) {
        throw new RuntimeException("ContentValues are no longer generated automatically. To enable it," +
                " set generateContentValues = true in @Table for $table.");
    }

    /**
     * If a [Model] has an auto-incrementing primary key, then
     * this method will be overridden.
     *
     * @param model The model object to store the key
     * @param id    The key to store
     */
    @Override
    public void updateAutoIncrement(T model, Number id) {

    }

    /**
     * Called when we want to save our [ForeignKey] objects. usually during insert + update.
     * This method is overridden when [ForeignKey] specified
     *
     * @param model model
     * @param wrapper wrapper
     */
    public void saveForeignKeys(T model, DatabaseWrapper wrapper) {

    }

    /**
     * Called when we want to delete our [ForeignKey] objects. During deletion [.delete]
     * This method is overridden when [ForeignKey] specified
     *
     * @param model model
     * @param wrapper wrapper
     */
    public void deleteForeignKeys(T model, DatabaseWrapper wrapper) {

    }

    @Override
    public boolean cachingEnabled() {
        return false;
    }

    public void setModelSaver(ModelSaver<T> _modelSaver){
        this._modelSaver = _modelSaver;
        _modelSaver.modelAdapter = this;
    }

    public ModelSaver<T> getModelSaver(){
        if(_modelSaver != null){
            return _modelSaver;
        }else {
            ModelSaver<T> singleModelSaver = createSingleModelSaver();
            singleModelSaver.modelAdapter = this;
            _modelSaver = singleModelSaver;
            return singleModelSaver;
        }
    }

    protected ModelSaver<T> createSingleModelSaver(){
        return new ModelSaver<T>();
    }

    protected ListModelSaver<T> createListModelSaver(){
        return new ListModelSaver<T>(getModelSaver());
    }

    /**
     * Retrieves a property by name from the table via the corresponding generated "_Table" class. Useful
     * when you want to dynamically get a property from an [ModelAdapter] and do an operation on it.
     *
     * @param columnName The column name of the property.
     * @return The property from the corresponding Table class.
     */
    public abstract Property<?> getProperty(String columnName);
}
